<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>SpringBoot系列—Run启动原理（十八） | 海晨忆的博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  
  
  
  <meta name="description" content="&amp;emsp;&amp;emsp;需要搞清楚几个重要的事件回调机制 配置在META-INF/spring.factories  ApplicationContextInitializer SpringApplicationRunListener  只需要放在ioc容器中  ApplicationRunner   CommandLineRunner">
<meta property="og:type" content="article">
<meta property="og:title" content="SpringBoot系列—Run启动原理（十八）">
<meta property="og:url" content="https://haichenyi.com/2019/11/11/SpringBoot系列-Run启动原理（十八）/index.html">
<meta property="og:site_name" content="海晨忆的博客">
<meta property="og:description" content="&amp;emsp;&amp;emsp;需要搞清楚几个重要的事件回调机制 配置在META-INF/spring.factories  ApplicationContextInitializer SpringApplicationRunListener  只需要放在ioc容器中  ApplicationRunner   CommandLineRunner">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://haichenyi.com/uploads/article/2019-11-11/spring_factories.png">
<meta property="og:updated_time" content="2019-11-11T09:32:08.591Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="SpringBoot系列—Run启动原理（十八）">
<meta name="twitter:description" content="&amp;emsp;&amp;emsp;需要搞清楚几个重要的事件回调机制 配置在META-INF/spring.factories  ApplicationContextInitializer SpringApplicationRunListener  只需要放在ioc容器中  ApplicationRunner   CommandLineRunner">
<meta name="twitter:image" content="https://haichenyi.com/uploads/article/2019-11-11/spring_factories.png">
  
    <link rel="alternate" href="/atom.xml" title="海晨忆的博客" type="application/atom+xml">
  
  
    <link rel="icon" href="/uploads/artistic_image/head.jpg">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  <link rel="stylesheet" href="/css/highlight.css">
  <script>
  let antiquityStorage = window.sessionStorage.getItem('antiquitySessionStorage');
  if (antiquityStorage == '' || antiquityStorage == null) {
    var antiquityLoader = '<div id="loaderbox"><div class="loader"><div class="load-roll"><div class="load-top"></div><div class="load-right"></div><div class="load-bottom"></div></div></div></div>';
    document.write(antiquityLoader);
    document.body.style.overflow = 'hidden'
  }
  </script>
</head>

<body>
  <div id="fullpage" class="mobile-nav-right">
    
      <div id="wrapper" style="background-image: url(/uploads/artistic_image/bg.jpg)" title="背景图片来自网络">
    
    
      <header id="header">
  <div id="nav-toggle" class="nav-toggle"></div>
  <div class="head-box global-width">
    <nav class="nav-box nav-right">
      
        <a class="nav-item" href="/" title
        
        >首页</a>
      
        <a class="nav-item" href="/archives" title
        
        >归档</a>
      
        <a class="nav-item" href="/about" title
        
        >简历</a>
      
    </nav>
  </div>
</header>
      <div id="middlecontent" title class="global-width sidebar-left">
        <section id="main"><article id="post-SpringBoot系列-Run启动原理（十八）" class="article global-container article-type-post" itemscope itemprop="blogPost">
  
    <header class="article-header">
      
  
    <h1 class="article-title" itemprop="name">
      SpringBoot系列—Run启动原理（十八）
    </h1>
  

    </header>
  
  <div class="article-meta">
    <a href="/2019/11/11/SpringBoot系列-Run启动原理（十八）/" class="article-date">
  <time datetime="2019-11-11T09:28:35.000Z" itemprop="datePublished">2019-11-11</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Java-SpringBoot/">Java -SpringBoot</a>
  </div>

    
  </div>
  
    <span id="busuanzi_container_page_pv">
      本文总阅读量<span id="busuanzi_value_page_pv"></span>次
    </span>
  

  <div class="article-inner">
    
    <div class="article-content article-content-cloud doorframe mac" itemprop="articleBody">
      
        <p>&emsp;&emsp;需要搞清楚几个重要的事件回调机制</p>
<p>配置在META-INF/spring.factories</p>
<ul>
<li>ApplicationContextInitializer</li>
<li>SpringApplicationRunListener</li>
</ul>
<p>只需要放在ioc容器中</p>
<ul>
<li><p>ApplicationRunner </p>
</li>
<li><p>CommandLineRunner</p>
</li>
</ul>
<a id="more"></a>
<p>&emsp;&emsp;新建一个空项目，就勾选web，找到启动类，每个方法上面写的注释，可以看一下：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">@SpringBootApplication</span><br><span class="line">public class SellApplication &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        SpringApplication.run(SellApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//上面run方法点进来</span><br><span class="line">public static ConfigurableApplicationContext run(Class&lt;?&gt; primarySource, String... args) &#123;</span><br><span class="line">		return run(new Class&lt;?&gt;[] &#123; primarySource &#125;, args);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">public static ConfigurableApplicationContext run(Class&lt;?&gt;[] primarySources, String[] args) &#123;</span><br><span class="line">		return new SpringApplication(primarySources).run(args);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>看到这里，就应该看到了，启动流程分为两步</p>
<ol>
<li>创建SpringApplication对象</li>
<li>运行run方法</li>
</ol>
<h3 id="创建SpringApplication对象"><a href="#创建SpringApplication对象" class="headerlink" title="创建SpringApplication对象"></a>创建SpringApplication对象</h3><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">//上面的构造方法点进去</span><br><span class="line">//这里与1.5版本不一样的地方就是，</span><br><span class="line">//2.X这里调用了重载的构造方法，而1.5这里调用的是一个initialize()方法，这个方法里面的内容，与下面两个参数的重载方法差不多</span><br><span class="line">public SpringApplication(Class&lt;?&gt;... primarySources) &#123;</span><br><span class="line">		this(null, primarySources);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">//下面这个就是this调用的重载的构造方法</span><br><span class="line">@SuppressWarnings(&#123; &quot;unchecked&quot;, &quot;rawtypes&quot; &#125;)</span><br><span class="line">public SpringApplication(ResourceLoader resourceLoader, Class&lt;?&gt;... primarySources) &#123;</span><br><span class="line">		this.resourceLoader = resourceLoader;</span><br><span class="line">		//保存主配置类，1.5里面这里有个非空判断，用if做的，这里换成的断言做判断</span><br><span class="line">		Assert.notNull(primarySources, &quot;PrimarySources must not be null&quot;);</span><br><span class="line">		this.primarySources = new LinkedHashSet&lt;&gt;(Arrays.asList(primarySources));</span><br><span class="line">		//判断当前是否一个web应用</span><br><span class="line">		this.webApplicationType = WebApplicationType.deduceFromClasspath();</span><br><span class="line">		//从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer；然后保存起 来</span><br><span class="line">		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));</span><br><span class="line">		//从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener</span><br><span class="line">		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));</span><br><span class="line">		//从多个配置类中找到有main方法的主配置类</span><br><span class="line">		this.mainApplicationClass = deduceMainApplicationClass();</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<h4 id="setInitializers-方法"><a href="#setInitializers-方法" class="headerlink" title="setInitializers()方法"></a>setInitializers()方法</h4><p>&emsp;&emsp;看方法名就知道，这个是初始化方法，初始化什么东西呢？再看传的参数ApplicationContextInitializer，就是一开始我们提到的类。我们看这个是怎么获取的</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line">//第一步：</span><br><span class="line">private SpringApplicationRunListeners getRunListeners(String[] args) &#123;</span><br><span class="line">		Class&lt;?&gt;[] types = new Class&lt;?&gt;[] &#123; SpringApplication.class, String[].class &#125;;</span><br><span class="line">		return new SpringApplicationRunListeners(logger,</span><br><span class="line">				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">//第二步：</span><br><span class="line">private &lt;T&gt; Collection&lt;T&gt; getSpringFactoriesInstances(Class&lt;T&gt; type, Class&lt;?&gt;[] parameterTypes, Object... args) &#123;</span><br><span class="line">		ClassLoader classLoader = getClassLoader();</span><br><span class="line">		// Use names and ensure unique to protect against duplicates</span><br><span class="line">		//看这里的导入方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)</span><br><span class="line">		Set&lt;String&gt; names = new LinkedHashSet&lt;&gt;(SpringFactoriesLoader.loadFactoryNames(type, classLoader));</span><br><span class="line">		List&lt;T&gt; instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);</span><br><span class="line">		AnnotationAwareOrderComparator.sort(instances);</span><br><span class="line">		return instances;</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">//第三步：</span><br><span class="line">public static List&lt;String&gt; loadFactoryNames(Class&lt;?&gt; factoryType, @Nullable ClassLoader classLoader) &#123;</span><br><span class="line">		String factoryTypeName = factoryType.getName();</span><br><span class="line">		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">//第四步，就是这里了。看这里面的实现</span><br><span class="line">private static Map&lt;String, List&lt;String&gt;&gt; loadSpringFactories(@Nullable ClassLoader classLoader) &#123;</span><br><span class="line">		MultiValueMap&lt;String, String&gt; result = cache.get(classLoader);</span><br><span class="line">		if (result != null) &#123;</span><br><span class="line">			return result;</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		try &#123;</span><br><span class="line">		//一眼看过去。很明显，这里就是classLoader.getResources()，导入的本地的资源。看这个传的参数,我放到这个方法下面去了</span><br><span class="line">			Enumeration&lt;URL&gt; urls = (classLoader != null ?</span><br><span class="line">					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :</span><br><span class="line">					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));</span><br><span class="line">			result = new LinkedMultiValueMap&lt;&gt;();</span><br><span class="line">			//这里通过一个while循环，加载本地配置的ApplicationContextInitializer</span><br><span class="line">			while (urls.hasMoreElements()) &#123;</span><br><span class="line">				URL url = urls.nextElement();</span><br><span class="line">				UrlResource resource = new UrlResource(url);</span><br><span class="line">				Properties properties = PropertiesLoaderUtils.loadProperties(resource);</span><br><span class="line">				for (Map.Entry&lt;?, ?&gt; entry : properties.entrySet()) &#123;</span><br><span class="line">					String factoryTypeName = ((String) entry.getKey()).trim();</span><br><span class="line">					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) &#123;</span><br><span class="line">						result.add(factoryTypeName, factoryImplementationName.trim());</span><br><span class="line">					&#125;</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">			cache.put(classLoader, result);</span><br><span class="line">			return result;</span><br><span class="line">		&#125;</span><br><span class="line">		catch (IOException ex) &#123;</span><br><span class="line">			throw new IllegalArgumentException(&quot;Unable to load factories from location [&quot; +</span><br><span class="line">					FACTORIES_RESOURCE_LOCATION + &quot;]&quot;, ex);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">/**</span><br><span class="line"> * The location to look for factories.</span><br><span class="line"> * &lt;p&gt;Can be present in multiple JAR files.</span><br><span class="line"> */</span><br><span class="line">public static final String FACTORIES_RESOURCE_LOCATION = &quot;META-INF/spring.factories&quot;;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;所以，从上面的源码，我们一步一步点击进去看，我们就能发现，他最终都是加载到 <strong>META-INF/spring.factories</strong> 目录下的 <strong>ApplicationContextInitializer</strong> 当然，到目前为止这里只是初始化</p>
<h4 id="setListeners-方法"><a href="#setListeners-方法" class="headerlink" title="setListeners()方法"></a>setListeners()方法</h4><p>&emsp;&emsp;一眼就能看出来，这里是设置监听方法</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;一眼看过去，这个setListener方法传的参数熟不熟悉？就是我们上面初始化的时候传的参数是同一个方法。所以，这里设置监听设置哪些监听方法也是<strong>META-INF/spring.factories</strong> 目录下的listener方法，我们看一下这个文件内容：</p>
<p><img src="/uploads/article/2019-11-11/spring_factories.png" alt="spring_factories.png"></p>
<p>&emsp;&emsp;这些都是是自动配置类的内容</p>
<h3 id="运行Run方法"><a href="#运行Run方法" class="headerlink" title="运行Run方法"></a>运行Run方法</h3><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">public ConfigurableApplicationContext run(String... args) &#123;</span><br><span class="line">		StopWatch stopWatch = new StopWatch();</span><br><span class="line">		stopWatch.start();</span><br><span class="line">		ConfigurableApplicationContext context = null;</span><br><span class="line">		Collection&lt;SpringBootExceptionReporter&gt; exceptionReporters = new ArrayList&lt;&gt;();</span><br><span class="line">		configureHeadlessProperty();</span><br><span class="line">		//获取SpringApplicationRunListeners；从类路径下META‐INF/spring.factories</span><br><span class="line">		SpringApplicationRunListeners listeners = getRunListeners(args);</span><br><span class="line">		//回调所有的获取SpringApplicationRunListener.starting()方法</span><br><span class="line">		listeners.starting();</span><br><span class="line">		try &#123;</span><br><span class="line">		    //封装命令行参数</span><br><span class="line">			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);</span><br><span class="line">			//准备环境，创建环境完成后回调SpringApplicationRunListener.environmentPrepared()；表示环境准 备完成</span><br><span class="line">			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);</span><br><span class="line">			//这里是新增的，点击去看，就是再properties文件中配置你需要忽略的bean</span><br><span class="line">			configureIgnoreBeanInfo(environment);</span><br><span class="line">			//这个是打印spring的logo banner图</span><br><span class="line">			Banner printedBanner = printBanner(environment);</span><br><span class="line">			/创建ApplicationContext；这个下面有下介绍</span><br><span class="line">			context = createApplicationContext();</span><br><span class="line">			//看一下参数，这个就是做异常报告处理的</span><br><span class="line">			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,</span><br><span class="line">					new Class[] &#123; ConfigurableApplicationContext.class &#125;, context);</span><br><span class="line">					</span><br><span class="line">			//准备上下文环境;将environment保存到ioc中；而且applyInitializers()；</span><br><span class="line">			//applyInitializers()：回调之前保存的所有的ApplicationContextInitializer的initialize方法</span><br><span class="line">			//回调所有的SpringApplicationRunListener的contextPrepared()；	</span><br><span class="line">			//prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded()；</span><br><span class="line">			prepareContext(context, environment, listeners, applicationArguments, printedBanner);</span><br><span class="line">			//s刷新容器；ioc容器初始化（如果是web应用还会创建嵌入式的Tomcat）；Spring注解版</span><br><span class="line">			//扫描，创建，加载所有组件的地方；（配置类，组件，自动配置）</span><br><span class="line">			refreshContext(context);</span><br><span class="line">			//2.x里面是空方法</span><br><span class="line">			afterRefresh(context, applicationArguments);</span><br><span class="line">			stopWatch.stop();</span><br><span class="line">			if (this.logStartupInfo) &#123;</span><br><span class="line">				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);</span><br><span class="line">			&#125;</span><br><span class="line">			listeners.started(context);</span><br><span class="line">			</span><br><span class="line">			//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 //ApplicationRunner先回调，CommandLineRunner再回调</span><br><span class="line">			callRunners(context, applicationArguments);</span><br><span class="line">		&#125;</span><br><span class="line">		catch (Throwable ex) &#123;</span><br><span class="line">			handleRunFailure(context, ex, exceptionReporters, listeners);</span><br><span class="line">			throw new IllegalStateException(ex);</span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		try &#123;</span><br><span class="line">			listeners.running(context);</span><br><span class="line">		&#125;</span><br><span class="line">		catch (Throwable ex) &#123;</span><br><span class="line">			handleRunFailure(context, ex, exceptionReporters, null);</span><br><span class="line">			throw new IllegalStateException(ex);</span><br><span class="line">		&#125;</span><br><span class="line">		//返回这个IOC容器</span><br><span class="line">		return context;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<h4 id="getRunListeners-方法"><a href="#getRunListeners-方法" class="headerlink" title="getRunListeners()方法"></a>getRunListeners()方法</h4><p>&emsp;&emsp;我们看到上面第一个有注释的位置：getRunListeners方法</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">private SpringApplicationRunListeners getRunListeners(String[] args) &#123;</span><br><span class="line">		Class&lt;?&gt;[] types = new Class&lt;?&gt;[] &#123; SpringApplication.class, String[].class &#125;;</span><br><span class="line">		return new SpringApplicationRunListeners(logger,</span><br><span class="line">				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;方法熟悉吗？就是我们上面初始化的时候调用的那个方法，只是这里的参数传的是：<strong>SpringApplicationRunListener</strong>,我们看最开始说的，这就是我们要了解的第二个内容，回调都是怎么调用的</p>
<p>&emsp;&emsp;方法里面的注释也写了，先获取监听事件，然后回调starting方法，我们看一下这个接口有那些回调方法：</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">public interface SpringApplicationRunListener &#123;</span><br><span class="line"></span><br><span class="line">	default void starting() &#123;</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	default void environmentPrepared(ConfigurableEnvironment environment) &#123;</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	default void contextPrepared(ConfigurableApplicationContext context) &#123;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	default void contextLoaded(ConfigurableApplicationContext context) &#123;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	default void started(ConfigurableApplicationContext context) &#123;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	default void running(ConfigurableApplicationContext context) &#123;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	default void failed(ConfigurableApplicationContext context, Throwable exception) &#123;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;就这些回调，这里还用了1.8的新特性，<strong>default关键字</strong>，接口里面的方法可以有方法体</p>
<h4 id="prepareEnvironment"><a href="#prepareEnvironment" class="headerlink" title="prepareEnvironment()"></a>prepareEnvironment()</h4><p>&emsp;&emsp;看到第二个写注释的位置，眼熟吗？可不就是跟上面回调方法名字相同么？我们点进去看一下</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,</span><br><span class="line">			ApplicationArguments applicationArguments) &#123;</span><br><span class="line">		// Create and configure the environment</span><br><span class="line">		ConfigurableEnvironment environment = getOrCreateEnvironment();</span><br><span class="line">		configureEnvironment(environment, applicationArguments.getSourceArgs());</span><br><span class="line">		ConfigurationPropertySources.attach(environment);</span><br><span class="line">		</span><br><span class="line">		//这里划重点，这里就调用的environmentPrepared的回调方法</span><br><span class="line">		listeners.environmentPrepared(environment);</span><br><span class="line">		bindToSpringApplication(environment);</span><br><span class="line">		if (!this.isCustomEnvironment) &#123;</span><br><span class="line">			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,</span><br><span class="line">					deduceEnvironmentClass());</span><br><span class="line">		&#125;</span><br><span class="line">		ConfigurationPropertySources.attach(environment);</span><br><span class="line">		return environment;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;准备完环境之后，调用environmentPrepared的回调</p>
<h4 id="createApplicationContext"><a href="#createApplicationContext" class="headerlink" title="createApplicationContext()"></a>createApplicationContext()</h4><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">protected ConfigurableApplicationContext createApplicationContext() &#123;</span><br><span class="line">		Class&lt;?&gt; contextClass = this.applicationContextClass;</span><br><span class="line">		if (contextClass == null) &#123;</span><br><span class="line">			try &#123;</span><br><span class="line">				switch (this.webApplicationType) &#123;</span><br><span class="line">				case SERVLET:</span><br><span class="line">					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);</span><br><span class="line">					break;</span><br><span class="line">				case REACTIVE:</span><br><span class="line">					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);</span><br><span class="line">					break;</span><br><span class="line">				default:</span><br><span class="line">					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);</span><br><span class="line">				&#125;</span><br><span class="line">			&#125;</span><br><span class="line">			catch (ClassNotFoundException ex) &#123;</span><br><span class="line">				throw new IllegalStateException(</span><br><span class="line">						&quot;Unable create a default ApplicationContext, please specify an ApplicationContextClass&quot;, ex);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;创建applicationContext，这里跟1.5不一样，1.5就只有两种：一种是web的ioc容器，一种是默认的ioc容器。2.X这里有三种：<strong>DEFAULT_CONTEXT_CLASS，DEFAULT_SERVLET_WEB_CONTEXT_CLASS，DEFAULT_REACTIVE_WEB_CONTEXT_CLASS</strong>，实际字符串比较长，可以去看一下源码。然后用BeanUtils通过反射创建。</p>
<h4 id="prepareContext-方法"><a href="#prepareContext-方法" class="headerlink" title="prepareContext()方法"></a>prepareContext()方法</h4><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,</span><br><span class="line">			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) &#123;</span><br><span class="line">		//将environment放到context中</span><br><span class="line">		context.setEnvironment(environment);</span><br><span class="line">		postProcessApplicationContext(context);</span><br><span class="line">		//初始化</span><br><span class="line">		applyInitializers(context);</span><br><span class="line">		//这里回调contextPrepared方法</span><br><span class="line">		listeners.contextPrepared(context);</span><br><span class="line">		if (this.logStartupInfo) &#123;</span><br><span class="line">			logStartupInfo(context.getParent() == null);</span><br><span class="line">			logStartupProfileInfo(context);</span><br><span class="line">		&#125;</span><br><span class="line">		// Add boot specific singleton beans</span><br><span class="line">		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();</span><br><span class="line">		beanFactory.registerSingleton(&quot;springApplicationArguments&quot;, applicationArguments);</span><br><span class="line">		if (printedBanner != null) &#123;</span><br><span class="line">			beanFactory.registerSingleton(&quot;springBootBanner&quot;, printedBanner);</span><br><span class="line">		&#125;</span><br><span class="line">		if (beanFactory instanceof DefaultListableBeanFactory) &#123;</span><br><span class="line">			((DefaultListableBeanFactory) beanFactory)</span><br><span class="line">					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);</span><br><span class="line">		&#125;</span><br><span class="line">		if (this.lazyInitialization) &#123;</span><br><span class="line">			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());</span><br><span class="line">		&#125;</span><br><span class="line">		// Load the sources</span><br><span class="line">		Set&lt;Object&gt; sources = getAllSources();</span><br><span class="line">		Assert.notEmpty(sources, &quot;Sources must not be empty&quot;);</span><br><span class="line">		load(context, sources.toArray(new Object[0]));</span><br><span class="line">		//这里回调contextLoaded()方法</span><br><span class="line">		listeners.contextLoaded(context);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;准备上下文环境;将environment保存到ioc中；而且applyInitializers()</p>
<div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">protected void applyInitializers(ConfigurableApplicationContext context) &#123;</span><br><span class="line">		for (ApplicationContextInitializer initializer : getInitializers()) &#123;</span><br><span class="line">			Class&lt;?&gt; requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),</span><br><span class="line">					ApplicationContextInitializer.class);</span><br><span class="line">			Assert.isInstanceOf(requiredType, context, &quot;Unable to call initializer.&quot;);</span><br><span class="line">			initializer.initialize(context);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;这里就将我们一开始从配置文件里面读取，然后创建ApplicationContextInitializer初始化。</p>
<p>&emsp;&emsp;当环境准备好之后，就回调了SpringApplicationRunListener的contextPrepared()；</p>
<p>&emsp;&emsp;当所有的都准备好了之后，回调SpringApplicationRunListener的contextLoaded()；</p>
<p>&emsp;&emsp;到这里，所有的环境都准备好了，需要打印的logo也加进去了。</p>
<h4 id="refreshContext"><a href="#refreshContext" class="headerlink" title="refreshContext()"></a>refreshContext()</h4><p>&emsp;&emsp;刷新容器这个方法，我们可以点到具体的功能实现里面，可以看到，这里就是扫描，创建，加载所有的组件，配置类，组件，自动配置等。</p>
<p>&emsp;&emsp;到这里，这个方法创建完之后，所有的控制器就创建完了，所有的组件，bean等，都在控制台打印出来了。如果是web应用，还会创建嵌入式的tomcat。我们spring boot项目内嵌tomcat，就是在这里创建的。</p>
<h4 id="afterRefresh"><a href="#afterRefresh" class="headerlink" title="afterRefresh()"></a>afterRefresh()</h4><p>&emsp;&emsp;1.5版本这个方法里面回调的是callRunners方法，而2.X版本，现在这是个空方法里面并没有实现。callRunners被提出来了，放到了最后面。</p>
<h4 id="started-running"><a href="#started-running" class="headerlink" title="started(),running()"></a>started(),running()</h4><p>&emsp;&emsp;在1.5版本这里，也就是afterRefresh()之后，应该是调用的SpringApplicationRunListeners的finished()方法。</p>
<p>&emsp;&emsp;在2.X版本之后，去掉了finished方法，改成了调用started方法，然后调用running方法。我们上面有一个starting方法，从这里名字就可以看出来，相当于，首先是正在启动当中，然后就是启动完成了，正在运行了。</p>
<h4 id="callRunners"><a href="#callRunners" class="headerlink" title="callRunners()"></a>callRunners()</h4><div class="highlight-box"autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" contenteditable="true"data-rel="PLAIN"><figure class="iseeu highlight /plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">private void callRunners(ApplicationContext context, ApplicationArguments args) &#123;</span><br><span class="line">		List&lt;Object&gt; runners = new ArrayList&lt;&gt;();</span><br><span class="line">		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());</span><br><span class="line">		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());</span><br><span class="line">		AnnotationAwareOrderComparator.sort(runners);</span><br><span class="line">		for (Object runner : new LinkedHashSet&lt;&gt;(runners)) &#123;</span><br><span class="line">			if (runner instanceof ApplicationRunner) &#123;</span><br><span class="line">				callRunner((ApplicationRunner) runner, args);</span><br><span class="line">			&#125;</span><br><span class="line">			if (runner instanceof CommandLineRunner) &#123;</span><br><span class="line">				callRunner((CommandLineRunner) runner, args);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure></div>
<p>&emsp;&emsp;从IOC容器中(不是配置文件)获取所有的的ApplicationRunner和CommandLineRunner进行回调。这也是最开始说的两个注意的地方</p>
<p>&emsp;&emsp;并且，这里有个先后顺序，先回调的ApplicationRunner，后回调的CommandLineRunner</p>
<p>&emsp;&emsp;这里也是run方法，最后执行的地方。从这里就是真正的开启了run。</p>
<p>&emsp;&emsp;最后一步，返回那个context就是返回IOC容器对象。到这里，我们的spring boot就启动完成了。</p>
<p>&emsp;&emsp;这就是我们的spring boot的启动原理。初始化，listener的回调，Runner的回调都说的很清楚。</p>

      
    </div>
    
      <footer class="article-footer">
        完
      </footer>
    
  </div>
  
    
<nav id="article-nav">
  <div class="article-nav-block">
    
      <a href="/2019/11/13/SpringBoot系列-Docker（十九）/" id="article-nav-newer" class="article-nav-link-wrap">
        <strong class="article-nav-caption"></strong>
        <div class="article-nav-title">
          
            SpringBoot系列-Docker（十九）
          
        </div>
      </a>
    
  </div>
  <div class="article-nav-block">
    
      <a href="/2019/11/06/SpringBoot系列—Redis（十七）/" id="article-nav-older" class="article-nav-link-wrap">
        <div class="article-nav-title">SpringBoot系列—Redis（十七）</div>
        <strong class="article-nav-caption"></strong>
      </a>
    
  </div>
</nav>

    
  
  
</article>
</section>
        <aside id="sidebar">
  
    <div class="widget-box">
  <div class="avatar-box avatar-item">
    <img class="avatar" src="/uploads/artistic_image/head.jpg" title="头像来自网络"></img>
    <h3 class="avatar-name">
      
        海晨忆
      
    </h3>
    <p class="avatar-slogan">
      先谋生，再谋爱。人间值得，未来可期。
    </p>
  </div>
</div>


  
    
  <div class="widget-box">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Android-Socket/">Android -Socket</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-加密算法/">Android -加密算法</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-常用功能/">Android -常用功能</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-框架/">Android -框架</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-源码解析/">Android -源码解析</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-自定义view/">Android -自定义view</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Android-设计模式/">Android -设计模式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/JAVA-并发/">JAVA -并发</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java-SpringBoot/">Java -SpringBoot</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Tomcat/">Tomcat</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/kotlin/">kotlin</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-JDBC/">数据库 -JDBC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/数据库-MySQL/">数据库 -MySQL</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-box">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/11/">November 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/10/">October 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/09/">September 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/08/">August 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/07/">July 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/05/">May 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/02/">February 2018</a></li></ul>
    </div>
  </div>

  
    
  <div class="widget-box">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2021/07/27/JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁/">JAVA—公平锁，非公平锁，悲观锁，乐观锁，死锁</a>
          </li>
        
          <li>
            <a href="/2021/07/19/JAVA—线程同步器AQS/">JAVA—线程同步器AQS</a>
          </li>
        
          <li>
            <a href="/2021/07/07/Android模拟点击/">Android模拟点击</a>
          </li>
        
          <li>
            <a href="/2019/11/27/SpringBoot系列-消息-RabbitMQ-（二十）/">SpringBoot系列-消息(RabbitMQ)（二十）</a>
          </li>
        
          <li>
            <a href="/2019/11/13/SpringBoot系列-Docker（十九）/">SpringBoot系列-Docker（十九）</a>
          </li>
        
      </ul>
    </div>
  </div>

  
      <div class="widget-box">
    <h3 class="widget-title">友情链接</h3>
    <div class="widget">
      
        <a class="hrf" style="display: block;" href="https://github.com/haichenyi" title target='_blank'
        >Github</a>
      
        <a class="hrf" style="display: block;" href="https://www.jianshu.com/u/6077ee440c37" title target='_blank'
        >简书</a>
      
        <a class="hrf" style="display: block;" href="https://blog.csdn.net/qq_27634797" title target='_blank'
        >CSDN</a>
      
    </div>
  </div>

  
</aside>
      </div>
      <footer id="footer">
  <div class="foot-box footers global-width">
    &copy;2017-2021 海晨忆 &nbsp;&nbsp;
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span id="busuanzi_container_site_pv">阁下是第<span id="busuanzi_value_site_pv"></span>个访客</span>
  </div>
</footer>
      <script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
if (!window.jQuery) {
var script = document.createElement('script');
script.src = "/js/jquery-2.0.3.min.js";
document.body.write(script);
}
</script>

  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



    </div>
    <nav id="mobile-nav" class="mobile-nav-box">
  <div class="mobile-nav-img mobile-nav-top"></div>
  
    <a href="/" class="mobile-nav-link">首页</a>
  
    <a href="/archives" class="mobile-nav-link">归档</a>
  
    <a href="/about" class="mobile-nav-link">简历</a>
  
  <div class="mobile-nav-img  mobile-nav-bottom"></div>
</nav>    
  </div>
</body>
</html>